package compiler;

import interfaces.SyntaxAnalyzer;

/**
 * Enforces grammer rules by making sure that the correct begin and end tags 
 * are in the correct places.
 * @author Zach Sierakowski
 *
 */

public class MySyntaxAnalyzer implements SyntaxAnalyzer {

	int paragraphVariables = 0;
	int boldVariables = 0;
	int italicsVariables = 0;
	
	@Override
	// #HAI head? body? #KTHXBYE
	public void lolcode() {
		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.DOC_BEGIN)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.DOC_BEGIN + " was expected");
			System.exit(0);
		}
		
		// add to parse tree
		MyCompiler.parseTree.add(MyCompiler.token);
		
		
		MyCompiler.Lexer.getNextToken();
		
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.COMMENT_BEGIN)){
			comment();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.DEFINE_BEGIN)){
			define();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.HEAD_BEGIN)){
			head();
		}
		
		body();
		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.DOC_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.DOC_END + " was expected");
			System.exit(0);
		}
		
		
		MyCompiler.parseTree.add(MyCompiler.token);
		
		if(!MyCompiler.Lexer.checkRestOfInput()){
			System.err.println("SYNTAX ERROR! No text after #KTHXBYE is allowed..");
			System.exit(0);
		}
	}

	@Override
	public void comment() {
		MyCompiler.parseTree.add(MyCompiler.token);
		
		MyCompiler.Lexer.getNextToken();	
		
		if(!MyCompiler.token.startsWith("#")){
			text();
		}
		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.COMMENT_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.COMMENT_END + " was expected");
			System.exit(0);
		}
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
	}

	@Override
	public void head() {
		MyCompiler.parseTree.add(MyCompiler.token);
		
		MyCompiler.Lexer.getNextToken();	
		
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.TITLE_BEGIN)){
			title();
		}
		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.HEAD_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.HEAD_END + " was expected");
			System.exit(0);
		}
		MyCompiler.parseTree.add(SemanticTokens.HEAD_END);
		MyCompiler.Lexer.getNextToken();
	}

	@Override
	public void title() {
		MyCompiler.parseTree.add(MyCompiler.token);
		
		MyCompiler.Lexer.getNextToken();	
		
		if(!MyCompiler.token.startsWith("#")){
			text();
		}
		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.TITLE_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.TITLE_END + " was expected");
			System.exit(0);
		}
		MyCompiler.parseTree.add(SemanticTokens.TITLE_END);
		MyCompiler.Lexer.getNextToken();
	}
	
	public void body(){
			
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.COMMENT_BEGIN)){
			comment();
			body();
		}
		innerText();
		
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.PARAGRAPH_BEGIN)){
			paragraph();
			body();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.BOLD_BEGIN)){
			bold();
			body();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITALICS_BEGIN)){
			italics();
			body();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.LIST_BEGIN)){
			list();
			body();
		}
		
	}

	@Override
	public void paragraph() {
		MyCompiler.parseTree.add(MyCompiler.token);
		
		MyCompiler.Lexer.getNextToken();	
		
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.DEFINE_BEGIN))
		{
			define();
			paragraphVariables++;
		}
		innerParagraph();

		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.PARAGRAPH_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.PARAGRAPH_END + " was expected");
			System.exit(0);
		}
		MyCompiler.parseTree.add(SemanticTokens.PARAGRAPH_END);
		MyCompiler.Lexer.getNextToken();
		
		for(int i = 0; i < paragraphVariables; i++)
		{
			MyCompiler.possibleTokens.remove(MyCompiler.possibleTokens.size() - 1);
		}
	}

	@Override
	public void bold() {
		
		MyCompiler.parseTree.add(MyCompiler.token );
		MyCompiler.Lexer.getNextToken();
		
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.DEFINE_BEGIN))
		{
			define();
			boldVariables++;
		}
		innerText();
		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.BOLD_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.BOLD_END + " was expected");
			System.exit(0);
		}
		
		MyCompiler.parseTree.add(SemanticTokens.BOLD_END);
		MyCompiler.Lexer.getNextToken();
		
		for(int i = 0; i < boldVariables; i++)
		{
			MyCompiler.possibleTokens.remove(MyCompiler.possibleTokens.size() - 1);
		}
		
	}

	@Override
	public void italics() {
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
		
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.DEFINE_BEGIN))
		{
			define();
			italicsVariables++;
		}
		innerText();
		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITALICS_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.ITALICS_END + " was expected");
			System.exit(0);
		}
		
		MyCompiler.parseTree.add(SemanticTokens.ITALICS_END);
		MyCompiler.Lexer.getNextToken();
		
		for(int i = 0; i < italicsVariables; i++)
		{
			MyCompiler.possibleTokens.remove(MyCompiler.possibleTokens.size() - 1);
		}

	}
	
	public void newline(){
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
		
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.NEWLINE)){
			newline();
		}
		
	}

	@Override
	public void list() {
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITEM_BEGIN))
		{
			System.err.println("SYNTAX ERROR: Need at least one list item... Expected #GIMMEH ITEM");
			System.exit(0);
		}else{
			MyCompiler.parseTree.add(MyCompiler.token);
			MyCompiler.Lexer.getNextToken();
			innerList();
			if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITEM_END))
			{
				System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
						+ LexicalTokens.ITEM_END + " was expected");
				System.exit(0);
			}else{
				MyCompiler.parseTree.add(SemanticTokens.ITEM_END);
				MyCompiler.Lexer.getNextToken();
				if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITEM_BEGIN))
				{
					item();
				}
				if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.LIST_END))
				{
					System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
							+ LexicalTokens.LIST_END + " was expected");
					System.exit(0);
				}
				MyCompiler.parseTree.add(SemanticTokens.LIST_END);
				MyCompiler.Lexer.getNextToken();
			}
		}
	
	}
	/**
	 * This method implements the BNF grammar rule for the innerList annotation.
	 */
	public void innerList(){
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.BOLD_BEGIN)){
			bold();
			innerList();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITALICS_BEGIN)){
			italics();
			innerList();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.LIST_BEGIN)){
			list();
			innerList();
		}
		innerText();
	}
	/**
	 * This method implements the BNF grammar rule for the innerParagraph annotation.
	 */
	public void innerParagraph(){
		innerText();
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.BOLD_BEGIN)){
			bold();
			innerParagraph();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITALICS_BEGIN)){
			italics();
			innerParagraph();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.LIST_BEGIN)){
			list();
			innerParagraph();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.NEWLINE)){
			newline();
			innerParagraph();
		}
	}

	@Override
	public void item() {
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
		
		innerList();
		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITEM_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.ITEM_END + " was expected");
			System.exit(0);
		}
		
		MyCompiler.parseTree.add(SemanticTokens.ITEM_END);
		MyCompiler.Lexer.getNextToken();
		
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITEM_BEGIN)){
			item();
		}
		
	}

	@Override
	public void audio() {
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
		
		if(!MyCompiler.token.startsWith("#")){
			text();
		}
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.AUDIO_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.AUDIO_END + " was expected");
			System.exit(0);
		}
		
		MyCompiler.parseTree.add(SemanticTokens.AUDIO_END);
		MyCompiler.Lexer.getNextToken();
	}

	@Override
	public void video() {
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
		
		if(!MyCompiler.token.startsWith("#")){
			text();
		}
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.VIDEO_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.VIDEO_END + " was expected");
			System.exit(0);
		}
		
		MyCompiler.parseTree.add(SemanticTokens.VIDEO_END);
		MyCompiler.Lexer.getNextToken();
		

	}

	@Override
	public void define() {
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
		
		if(MyCompiler.token.startsWith("#")){
			System.err.println("SYNTAX ERROR: need text after #I HAS A to define the variable reference");
			System.exit(0);
		}
		variable();
		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITZ)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.ITZ + " was expected");
			System.exit(0);
		}
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
		
		if(MyCompiler.token.startsWith("#")){
			System.err.println("SYNTAX ERROR: need text after #ITZ to define the variable's context");
			System.exit(0);
		}
		variableDef();
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.DEFINE_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.DEFINE_END + " was expected");
			System.exit(0);
		}
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.DEFINE_BEGIN))
		{
			define();
		}
	}

	@Override
	public void use() {
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
		
		if(MyCompiler.token.startsWith("#")){
			System.err.println("SYNTAX ERROR: need text after #VISIBLE to define the variable you would like to use..");
			System.exit(0);
		}
		
		while(MyCompiler.token.endsWith(" ")){
			MyCompiler.token = MyCompiler.token.substring(0, MyCompiler.token.length() - 1);
		}
		
		String s = "";

		for(int i = MyCompiler.possibleTokens.size() - 1; i >= 0; i--){
			if(MyCompiler.possibleTokens.get(i).lol.equals(MyCompiler.token)){
				s = MyCompiler.possibleTokens.get(i).html;
				break;
			}
		}
		
		if(s.isEmpty()){
			System.err.println("SYNTAX ERROR: Variable " + MyCompiler.token + " is undefined..");
			System.exit(0);
		}
		
		MyCompiler.parseTree.add(s);
		MyCompiler.Lexer.getNextToken();
		
		if(!MyCompiler.token.equalsIgnoreCase(LexicalTokens.USE_END)){
			System.err.println("SYNTAX ERROR: Found " + MyCompiler.token + " when "
					+ LexicalTokens.USE_END + " was expected");
			System.exit(0);
		}
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
		
	}
	/**
	 * This method implements the BNF grammar rule for the innerText annotation.
	 */
	public void innerText() {
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.USE_BEGIN)){
			use();
			innerText();
		}
		if(!MyCompiler.token.startsWith("#")){
			text();
			innerText();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.NEWLINE)){
			newline();
			innerText();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.AUDIO_BEGIN)){
			audio();
			innerText();
		}
		if(MyCompiler.token.equalsIgnoreCase(LexicalTokens.VIDEO_BEGIN)){
			video();
			innerText();
		}
	}
	/**
	 * This method implements the BNF grammar rule for the text annotation.
	 */
	public void text() {
		MyCompiler.parseTree.add(MyCompiler.token);
		MyCompiler.Lexer.getNextToken();
	}
	/**
	 * This method is a variation of the text method that makes sure the variable name is only one word
	 */
	public void variable() {
		while(MyCompiler.token.endsWith(" ")){
			MyCompiler.token = MyCompiler.token.substring(0, MyCompiler.token.length() - 1);
		}
		if(MyCompiler.token.contains(" ")){
			System.err.println("SYNTAX ERROR! Variables cannot be multiple words..");
			System.exit(0);
		}
		MyCompiler.possibleTokens.add(new DictionaryTokenTuple<String, String>(MyCompiler.token, ""));
		MyCompiler.Lexer.getNextToken();
	}
	/**
	 * This method converts the variable name to the defined variable value
	 */
	public void variableDef() {
		MyCompiler.possibleTokens.get(MyCompiler.possibleTokens.size()- 1).html = " " + MyCompiler.token + " ";
		MyCompiler.Lexer.getNextToken();
	}
}
